package lift_simulator;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * <pre>
 * 通常のリフト管理プログラムです。以下のルールに基づいて動作します。
 * ・リフトに人を載せる場合、乗っている人間の方向が同じでなければいけない。
 * ・人が乗っていない場合はどちらも載せることができる。
 * ・リフトは極力対称的に動作させる。
 * </pre>
 * @author Yone6379univ
 *
 */
public class LiftManagerNormal extends LiftManager {
	//リフトが乗せると宣言したリスト
	ArrayList<Task> entryTasks = new ArrayList<Task>();
	//最後に指定した方向
	Direction lastDirection = Direction.UP;
	
	@Override
	public Aim getAim(Lift lift) {
		//初めてのgetAimかどうか
		if(lift.getAim()==null){
			Direction direction = Direction.getDirection(lift.getCurrentFloor(), 5);
			return new Aim(5, direction, null, null,1);
		}
		
		//リフトがタスクを持っている場合
		if(lift.getTasks().size()!=0){
			//目的階を保有するタスク内で最寄りのものに決定
			int nearestFloor = getNearestFloor(lift);
			//そのタスク以前に進行方向が同じタスクがある階を探す
			//あればその階に変更
			int pickUpFloor = getPickUp(lift, nearestFloor);
			if(pickUpFloor!=-1)
				nearestFloor=pickUpFloor;
			//リフトが向かう方向
			Direction direction = Direction.getDirection(lift.getCurrentFloor(), nearestFloor);
			//その目的階で格納するタスクと降車するタスクを登録			
			//降車するタスク
			ArrayList<Task> descendTasks = getDescendTasks(lift, nearestFloor);
			//その階の降車で保有するタスクが無くなる場合
			if(lift.getTasks().size()==descendTasks.size()){
				//次にどちらに進むかを決める(多数決)
				Direction nextDirection = getNextDirection(lift);
				ArrayList<Task> boardTasks = getBoardTasks(nearestFloor, nextDirection,lift,descendTasks.size());				
				Aim aim = new Aim(nearestFloor, direction, getIntArray(boardTasks), getIntArray(descendTasks), 5);
				return aim;
			}
			//その階の降車で保有するタスクが無くならない場合
			else{
				//同じ方向のタスクを格納
				ArrayList<Task> boardTasks = getBoardTasks(nearestFloor, direction,lift,descendTasks.size());
				Aim aim = new Aim(nearestFloor, direction, getIntArray(boardTasks), getIntArray(descendTasks), 5);
				return aim;
			}			
		}
		//リフトがタスクを持っていない場合
		else{
			//自分以外の各リフトが上昇中か、下降中かの多数決をとる。
			Direction nextDirection = getNextDirection(lift);
			//次に移動すべき階を取得
			int nextFloor = getNextFloor(nextDirection);
			//方向を更新
			Direction newDirection = Direction.getDirection(lift.getCurrentFloor(), nextFloor);
			if(nextFloor==-1){
				//タスクが無ければ５階で待機
				Direction direction = Direction.getDirection(lift.getCurrentFloor(), 5);
				return new Aim(5, direction, null, null,1);
			}else{
				//その階で乗車するタスクを取得
				ArrayList<Task> boardTasks = getBoardTasks(nextFloor, nextDirection,lift,0);
				Aim aim = new Aim(nextFloor, newDirection, getIntArray(boardTasks), null, 5);
				return aim;
			}
		}
	}
	
	/**
	 * <pre>
	 * リフトの次の進行方向を取得します。
	 * ビルに登録されているリフトの方向を統計し、結果と逆方向に進みます
	 * 統計結果が均衡した場合は引数のリフトと反対方向を返します
	 * TODO 相方が下に進んでても、相方がもうすぐ下に到着するなら自分も下に向かうべき
	 * @return
	 */
	private Direction getNextDirection(Lift lift){
		//リフトが一台だと変わらないので交互にする
		if(building.getLifts().size()==1){
			if(lift.getAim().getDirection()==Direction.UP)
				return Direction.DOWN;
			else if(lift.getAim().getDirection()==Direction.DOWN)
				return Direction.UP;
			else{
				return getAverageDirection();
			}
		}
		
		ArrayList<Lift> lifts = building.getLifts();
		double sum=0;
		for(Lift l:lifts){
			if(l.getId()!=lift.getId())
				sum+=getPoint(l, building.getFloorSize());//l.getAim().getDirection().getValue();
		}
		//結果が上昇なら→下降
		if(sum>0)
			return Direction.DOWN;
		//結果が下降なら→上昇
		else if(sum<0)
			return Direction.UP;
		//均衡なら→過去の履歴の反対
		else{
			switch (lastDirection) {
			case DOWN:
				lastDirection=Direction.UP;
				return Direction.UP;
			case UP:
				lastDirection=Direction.DOWN;
				return Direction.DOWN;
			default:
				return Direction.STOP;
			}
		}
	}
	
	/**
	 * <pre>
	 * リフトの次の方向を決める上でのポイントを計算します。
	 * 下降中の場合
	 * 階の半分以上なら-
	 * 階の半分未満なら+
	 * 上昇中の場合
	 * 階の半分以下なら+
	 * 階の半分より上なら-
	 * 停止中の場合
	 * 0
	 * @param lift
	 * @return
	 */
	private double getPoint(Lift lift, int buildingSize){
		Direction d = lift.getAim().getDirection();
		double currentFloor = lift.getCurrentFloor();
		double eval = buildingSize/2;
		switch (d) {
		case DOWN:
			if(currentFloor>=eval)
				return Direction.DOWN.getValue();
			else
				return Direction.UP.getValue();
		case UP:
			if(currentFloor<=eval)
				return Direction.UP.getValue();
			else
				return Direction.DOWN.getValue();
		default:
			return 0;
		}
	}
	
	/**
	 * ビル全体の乗車可能なタスクの方向が多い方を返します。
	 */
	private Direction getAverageDirection(){
		int countUp=0,countDown=0;
		for(int i=1;i<=building.getFloorSize();i++){
			for(Task t:getTasks(building.getFloor(i), true)){
				Direction d = Direction.getDirection(t.getBoard(), t.getDescend());
				if(d==Direction.UP)
					countUp++;
				else
					countDown++;				
			}
		}
		if(countUp>countDown)
			return Direction.UP;
		else
			return Direction.DOWN;
	}
	
	/**
	 * <pre>
	 * 指定された方向における、乗車可能なタスクを保持したフロアを返します。
	 * 上昇なら→下の階から順にタスクを探す
	 * 下降なら→上の階から順にタスクを探す
	 * </pre>
	 * @param direction
	 * @return
	 */
	private int getNextFloor(Direction direction){
		int floorSize = building.getFloorSize();
		boolean isUp = direction==Direction.UP;
		int x = isUp? 1:-1;
		for(int i=isUp? 1 : floorSize;
		isUp? i<=floorSize : i>0;
		i+=1*x){
			Floor floor = building.getFloor(i);
			for(Task task:getTasks(floor, true)){
				//タスクの方向性
				Direction taskDirection = Direction.getDirection(task.getBoard(), task.getDescend());
				if(taskDirection==direction)
					return task.getBoard();
			}
		}
		return -1;
	}
	
	/**
	 * 引数のリフト内のタスクで最も目的階が現在の階に近い階を返します。
	 * @param lift
	 * @return 対象となる階が無ければ-1を返します
	 */
	private int getNearestFloor(Lift lift){
		ArrayList<Task> tasks = lift.getTasks();
		int sub=Integer.MAX_VALUE;
		int floor=-1;
		for(Task t : tasks){
			int targetFloor = t.getDescend();
			int targetSub = (int)Math.abs(targetFloor-lift.getCurrentFloor());
			if(targetSub<sub)
				floor = targetFloor;
		}
		return floor;
	}
	
	/**
	 * 引数のリフトの現在の階の次から引数の目的階の前までの階に存在する、同じ方向のタスクが
	 * 存在する最寄りの階を取得します
	 * @return
	 */
	private int getPickUp(Lift lift, int targetFloor){
		int currentFloor = (int)lift.getCurrentFloor();
		//指定されたリフトの方向
		Direction direction = Direction.getDirection(currentFloor, targetFloor);
		
		int x = direction==Direction.DOWN? -1:1;
		//現在階+１から到着階-１までチェック
		for(int i=currentFloor+1*x;
				x==1?i<targetFloor:i>targetFloor
				;i+=1*x){
			//フロア取得
			Floor f = building.getFloor(i);
			//現在乗車可能なタスク一覧の取得
			for(Task task : getTasks(f, true)){
				//タスクの方向性
				Direction taskDirection = Direction.getDirection(task.getBoard(), task.getDescend());
				if(taskDirection==direction)
					return task.getBoard();
			}
		}
		return -1;
	}
	
	/**
	 * 指定されたリフトのタスクリストの内、指定された階数で降車するタスクの一覧を返します。
	 * @param lift
	 * @param floor
	 * @return
	 */
	private ArrayList<Task> getDescendTasks(Lift lift, int floor){
		ArrayList<Task> ret = new ArrayList<Task>();
		for(Task t:lift.getTasks()){
			if(t.getDescend()==floor)
				ret.add(t);
		}
		return ret;
	}
	
	/**
	 * 指定されたフロアにおいて、指定された方向と同じタスクの一覧を返します。
	 * また、引数に現在のリフトとこれから降りる予定の人数を引数に与えることで、
	 * 容量を調節しています。
	 * @param floor
	 * @param direction
	 * @param currentNum
	 * @param descendNum
	 * @return
	 */
	private ArrayList<Task> getBoardTasks(int floor, Direction direction,Lift lift, int descendNum){
		//載せることができる人数
		int canRideNum = lift.getCapacity()+descendNum-lift.getTasks().size();
		ArrayList<Task> ret = new ArrayList<Task>();
		Floor floor2 = building.getFloor(floor);
		int count=0;
		for(Task task:getTasks(floor2, true)){
			if(count>=canRideNum)
				break;
			//タスクの方向性
			Direction taskDirection = Direction.getDirection(task.getBoard(), task.getDescend());
			if(taskDirection==direction){
				ret.add(task);
				entryTasks.add(task);
				count++;
			}
		}
		return ret;
	}
	
	/**
	 * 指定されrたタスクのリストを配列要素５個以内のint配列に変換します。
	 * @param tasks
	 * @return
	 */
	private int[] getIntArray(ArrayList<Task> tasks){
		ArrayList<Integer> ret0 = new ArrayList<Integer>();
		int count=0;
		for(Task t : tasks){
			if(count>4)
				break;
			ret0.add(t.getId());
			count++;
		}
		int[] ret = new int[ret0.size()];
		for(int i=0;i<ret.length;i++){
			ret[i]=ret0.get(i);
		}
		return ret;
	}
	
	/**
	 * getAimが初めて起動したかをチェックします。
	 * @return
	 */
	private boolean isFirst(){
		//初期化処理
		ArrayList<Lift> lifts = building.getLifts();
		boolean isFirst=true;
		for(Lift l:lifts){
			isFirst&=l.getAim()==null;
		}
		return isFirst;
	}
	/**
	 * 指定された階において、乗車可能なタスクを返します。
	 * すでに他のリフトが取りに行ったタスクは返りません。
	 * @param floor
	 * @param onlyCantRide
	 * @return
	 */
	private ArrayList<Task> getTasks(Floor floor, boolean onlyCantRide){
		ArrayList<Task> ret = new ArrayList<Task>();
		ArrayList<Task> tasks = floor.getTasks(onlyCantRide);
		for(Task t:tasks){
			if(!entryTasks.contains(t))
				ret.add(t);
		}
		return ret;
	}
}
